#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <signal.h>
#include <pthread.h>
#include "random.h"
#include "errlib.h"
#include "cityADT.h"
#include "carADT.h"
#include "semaphoreADT.h"
#include "confAdmADT.h"
#include "drawerADT.h"
#include "trafficSimulator.h"
#include <string.h>

#define SEM_INFO_DISPLAY "Number of semaphores: "
#define CAR_INFO_DISPLAY "Number of cars: "
#define NUM_OF_BLOCKS_DISPLAY "Number of blocks: " 
#define CAR_SPEED_DISPLAY "Car's speed multiplier: " 
#define SEM_SPEED_DISPLAY "Semaphore speed increase/decrease: "
#define CM_HEAD_INFO "Commands: "
#define COMMAND_INFO_DISPLAY1 ">c: create car          >v: remove car         >s: add semaphore "
#define COMMAND_INFO_DISPLAY2 ">d: remove semaphore    >+: increase car speed >-: decrease car speed "
#define COMMAND_INFO_DISPLAY3 ">r: restart car's speed                        >t: decrease semaphore speed(1 sec)"
#define COMMAND_INFO_DISPLAY4 ">y: increase semaphore speed(1 sec)            >m: increase simulation speed"
#define COMMAND_INFO_DISPLAY5 ">n: decrease simulation speed'"

#define MAX_INFO_WIDTH 45
#define MAX_NUM_CARS 50
#define MAX_CAR_SIM_SPEED 600

#define MIN(a, b)	(((a) < (b))? (a) : (b))


enum info_line{SEM_INFO, CAR_INFO, BLOCKS_INFO, SPEED_INFO, SEM_SP_INFO, CM_HEAD, C1, C2, C3, C4, C5};

/* global simulation variable data type */
typedef struct simulation
{
	confAdmADT conf;
	int confValues[NUM_CONF_KEYS];
	
	drawerADT drawer;
	pthread_t drawerThread;
	
	cityADT city;
	
	carADT * cars;
	int num_cars;
	
	semaphoreADT * semaphores;
	pthread_t * semThreads;
	int num_semaphores;

	double speedMultiplier;
	int semSpeedIncrement;
	
} simulationT; 


/* global simulation variable */
static simulationT simulation;
/* auto increment that generates cars ID */
static int carID = 0;
/* auto increment that generates semahores ID */
static int semID = 0;
/* auto increment that generates random car speed */
static int carSpeed = 0;
/* semaphore speed expressed in seconds*/
static int semSpeed;
/* array of available X position to initialize cars */
static int * arrayXPos;
/* array of available Y position to initialize cars */
static int * arrayYPos;
/* global pthread_attr_t variable */
static pthread_attr_t attr;


/* get the value of a key or set default in case is not found */	
static void getConfValue(char * key, int * ans, int def);
/* create a car object and its thread */
static void createCar(void);
/* create a semaphores object and its thread */
static void createSemaphore(void);
/* delete a car */
static void destroyCar(void);
/* delete a car */
static void destroySemaphore(void);
/* multiplies speed of all cars */
static void changeCarsSpeed(double mlt);
/* reset speed of all cars */
static void resetCars(void);
/* change duration of all semaphores */
static void changeSemDuration(int step);

/*prints the initial info screen, including help information*/
static void printInitialInfo();

/* actualize and draw the information of the simulation to be displayd */
static void actalizeInfoInScreen(int infoType);


/* trafficSimulator main function */
int 
main(void)
{
	/* initialize simulation */
	initSimulation();
	
	/* start simulation */
	simulate();
	
	/* stop simulation and quit program */
	finishSimulation(0);
	return 0;
}

/* set signals handlers */
void
setSignalCatcher(void)
{
	signal(SIGINT, finishSimulation);
	signal(SIGTERM, finishSimulation);
	signal(SIGQUIT, finishSimulation);
	signal(SIGALRM, finishSimulation);
}

/* create instances of objects needed to start the simulation */ 
void
initSimulation(void)
{
	/* defines exit program function */
	atexit(exitSimulation);
	/* set signals handlers */
	setSignalCatcher();
	/* set random seed */
	randomize();

	pthread_attr_init(&attr);
	pthread_attr_setstacksize(&attr, STACK_SIZE);
	//pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
	

	/* initialization of all objects */
	initConfig();
	initOutput();
	initCity();
	initSemaphores();
	initCars();
	printInitialInfo();
}

static void
printInitialInfo()
{
	printInfo(simulation.drawer, CM_HEAD, CM_HEAD_INFO);
	printInfo(simulation.drawer, C1, COMMAND_INFO_DISPLAY1);
	printInfo(simulation.drawer, C2, COMMAND_INFO_DISPLAY2);
	printInfo(simulation.drawer, C3, COMMAND_INFO_DISPLAY3);
	printInfo(simulation.drawer, C4, COMMAND_INFO_DISPLAY4);
	printInfo(simulation.drawer, C5, COMMAND_INFO_DISPLAY5);
	actalizeInfoInScreen(BLOCKS_INFO);
	actalizeInfoInScreen(SPEED_INFO);
	actalizeInfoInScreen(SEM_SP_INFO);
}

/* read and process input while simulation is running */ 
void
simulate(void)
{
	int command;

	/* press 'q' or 'Q' to exit */
	while((command = getchar()) != 'q' && command != 'Q')
	{
		processInput(command);	
	}
}

/* stop simulation and quit program */
void
finishSimulation(int a)
{
	/* main thread exit */
	//pthread_exit(NULL);
	exit(EXIT_SUCCESS);
}

/* initialize config administration object */
void
initConfig(void)
{
	/* open configuration file with confAdmADT */
	simulation.conf = NewConfAdm();
	if (!initConfAdm(simulation.conf, CONFIG_FILE))
		fatal("trafficSimulator: Error while opening config file");
	
	/* read and store configuration values */
	getConfValue(KEY_NUM_BLOCKS_X, &(simulation.confValues[NUM_BLOCKS_X]), DEF_NUM_BLOCKS_X);
	getConfValue(KEY_NUM_BLOCKS_Y, &(simulation.confValues[NUM_BLOCKS_Y]), DEF_NUM_BLOCKS_Y);
	getConfValue(KEY_BLOCK_SIZE_X, &(simulation.confValues[BLOCK_SIZE_X]), DEF_BLOCK_SIZE_X);
	getConfValue(KEY_BLOCK_SIZE_Y, &(simulation.confValues[BLOCK_SIZE_Y]), DEF_BLOCK_SIZE_Y);
	getConfValue(KEY_INIT_CARS, &(simulation.confValues[INIT_CARS]), DEF_INIT_CARS);
	getConfValue(KEY_INIT_SEMAPHORES, &(simulation.confValues[INIT_SEMAPHORES]), DEF_INIT_SEMAPHORES);
	getConfValue(KEY_SEM_TIME, &semSpeed, DEF_SEM_TIME);	

	simulation.speedMultiplier = 1.0;
	simulation.semSpeedIncrement = semSpeed;
}

/* initialize output drawer thread */
void
initOutput(void)
{
	/* create drawer object */
	if((simulation.drawer = newDrawer(simulation.confValues[BLOCK_SIZE_X], 
		simulation.confValues[BLOCK_SIZE_Y], simulation.confValues[NUM_BLOCKS_X], 
		simulation.confValues[NUM_BLOCKS_Y])) == NULL)
		fatal("trafficSimulator: Error while initializing drawer object");
	
	/* create drawer thread */
	if (pthread_create(&(simulation.drawerThread), &attr, 
		(void * (*)(void*)) draw, (void *) (simulation.drawer)))
		fatal("trafficSimulator: Error while initializing drawer thread");
}

/* initialize city object */
void
initCity()
{
	/* create city object */
	if((simulation.city = newCity(simulation.confValues[NUM_BLOCKS_X], 
		simulation.confValues[NUM_BLOCKS_Y], simulation.confValues[BLOCK_SIZE_X], 
		simulation.confValues[BLOCK_SIZE_Y])) == NULL)
		fatal("trafficSimulator: Error while initializing city object");
}

/* initialize cars object */
void
initCars(void)
{
	int i, j, data;
	
	simulation.num_cars = 0;
	simulation.cars = NULL;

	if ((arrayXPos = malloc(simulation.confValues[NUM_BLOCKS_X] * simulation.confValues[BLOCK_SIZE_X] 
		* sizeof(int))) == NULL)
		fatal("trafficSimulator: Error while initializing cars objects");

	if ((arrayYPos = malloc(simulation.confValues[NUM_BLOCKS_Y] * simulation.confValues[BLOCK_SIZE_Y] 
		* sizeof(int))) == NULL)
		fatal("trafficSimulator: Error while initializing cars objects");

	for (i = 0, data = 2; i < simulation.confValues[NUM_BLOCKS_X] *
		simulation.confValues[BLOCK_SIZE_X]; data += 2)
		       	for (j = 0; j < simulation.confValues[BLOCK_SIZE_X]; j++)
				arrayXPos[i++] = data++;

	for (i = 0, data = 1; i < simulation.confValues[NUM_BLOCKS_Y]; i++)
	{
	       	arrayYPos[i] = data;
		data += simulation.confValues[BLOCK_SIZE_Y] + 2;
	}			

	/* create initial cars */
	for (i = 0; i < simulation.confValues[INIT_CARS]; i++)
		createCar();
}

/* initialize semaphores object */
void
initSemaphores(void)
{
	int i;
	
	simulation.num_semaphores = 0;	
	simulation.semaphores = NULL;
	simulation.semThreads = NULL;
	
	/* create initial semaphores */
	for (i = 0; i < simulation.confValues[INIT_SEMAPHORES]; i++)
		createSemaphore();
}
	
/* free memory of all objects created */ 	
void
exitSimulation(void)
{
	int i;
	
	/* destroy cars objects */
	for (i = 0; i < simulation.num_cars; i++)
		destroyCar();

	/* free cars object and threads arrays */
	free(simulation.cars);
	free(arrayXPos);
	free(arrayYPos);
	
	/* destroy semaphores objects */
	for (i = 0; i < simulation.num_semaphores; i++)
		destroySemaphore();
		
	/* free semaphores object and threads arrays */
	free(simulation.semaphores);
	free(simulation.semThreads);
	
	/* free config administrator object */
	freeConfAdm(simulation.conf);
	/* free city object */
	freeCity(simulation.city);
	/* free drawer object */
	freeDrawer(simulation.drawer);
	/* destroy pthread_attr_t */
	pthread_attr_destroy(&attr);
}

/* process input and change the simulation stage */
void
processInput(int cmd)
{
	//char s1[3], s2[3];

	switch(toupper(cmd))
	{
		case 'C': createCar(); break;
		case 'V': destroyCar(); break;
		case 'S': createSemaphore(); break;
		case 'D': destroySemaphore(); break;
		case 'R': resetCars(); break;
		case 'T': changeSemDuration(1); break;
		case 'Y': changeSemDuration(-1); break;
		case 'M': changeCarsSpeed(DOUBLE);
			  changeSemDuration(1);
			  break;
		case 'N': changeCarsSpeed(HALF);
			  changeSemDuration(-1);
			  break;			  
		case '-': changeCarsSpeed(HALF); break;
		case '+': changeCarsSpeed(DOUBLE); break;
		/*case 'J': 
			sprintf(s1,"%d",getCarX(simulation.cars[0]));
			printInfo(simulation.drawer, C5+1, s1);
			sprintf(s2,"%d",getCarY(simulation.cars[0]));
			printInfo(simulation.drawer, C5+2, s2);
				
			drawInfo(simulation.drawer);*/
		default: break;
	}
}


/*********************
** Auxiliar functions
*********************/ 


/* max size of configuration value */
#define MAX_VALUE_SIZE		256

/* get the value of a key or set default in case is not found */
static void
getConfValue(char * key, int * ans, int def)
{
	char buffer[MAX_VALUE_SIZE];
	char * aux = buffer;
	
	if (findValue(simulation.conf, key, &aux))
		*ans = atoi(aux);
	else
		*ans = def;
}

/* create a car object and its thread */
static void
createCar(void)
{
	int i, j, created;

	created = 0;
	
	if (simulation.num_cars == MIN(simulation.confValues[NUM_BLOCKS_X] * simulation.confValues[NUM_BLOCKS_Y], MAX_NUM_CARS))
		return;

	/* allocating space for a new car object */
	if ((simulation.cars = realloc(simulation.cars, (simulation.num_cars + 1) * sizeof(carADT)))
		 == NULL)
		fatal("trafficSimulator: Error while allocating memory");

	while (!created)
	{	
		i = randInt(0, simulation.confValues[NUM_BLOCKS_X] *
			 simulation.confValues[BLOCK_SIZE_X]- 1);
		j = randInt(0, simulation.confValues[NUM_BLOCKS_Y] - 1);
		i = arrayXPos[i];
		j = arrayYPos[j];

		if(hasThisPositionACar(simulation.city, i, j, -1) != NULL)
			continue;

		/* create new car object */
		if ((simulation.cars[simulation.num_cars] = newCar(carSpeed % NUM_CAR_SPEEDS, i, j,
		simulation.city, W, 0, carID, simulation.drawer)) == NULL)
			fatal("trafficSimulator: Error while initializing car object");

		registerCar(simulation.city, simulation.cars[simulation.num_cars]);
		created = 1;
	}
	
	carSpeed++;
	carID++;
	multiplySpeed(simulation.cars[simulation.num_cars], simulation.speedMultiplier);
	simulation.num_cars++;

	actalizeInfoInScreen(CAR_INFO);
}

/* create a semaphores object and its thread */
static void
createSemaphore(void)
{
	int i, j, created;

	created = 0;
	
	if (simulation.num_semaphores == (simulation.confValues[NUM_BLOCKS_X] * simulation.confValues[NUM_BLOCKS_Y]))
		return;

	/* allocating space for a new semaphore object */
	if ((simulation.semaphores = realloc(simulation.semaphores, (simulation.num_semaphores + 1) * sizeof(semaphoreADT))) == NULL)
		fatal("trafficSimulator: Error while allocating memory");

	while(!created)
	{
		i = randInt(0, simulation.confValues[NUM_BLOCKS_X] - 1);
		j = randInt(0, simulation.confValues[NUM_BLOCKS_Y] - 1);

		i = i * (simulation.confValues[BLOCK_SIZE_X] + 2);
		j = j * (simulation.confValues[BLOCK_SIZE_Y] + 2);

		if (hasSemaphore(simulation.city, i, j))
			continue;

		/* create new semaphore object */
		if ((simulation.semaphores[simulation.num_semaphores] = newSemaphore(semSpeed, i, j, simulation.drawer)) == NULL)
			fatal("trafficSimulator: Error while initializing semaphore object");

		if (registerSemaphore(simulation.city, simulation.semaphores[simulation.num_semaphores], i, j) != 1)
			fatal("trafficSimulator: Error while registering semaphore object");
		created = 1;
	}

	/* allocating space for thread */
	if ((simulation.semThreads = realloc(simulation.semThreads, (simulation.num_semaphores + 1) * sizeof(pthread_t))) == NULL)
		fatal("trafficSimulator: Error while allocating memory");

	/* create semaphore thread */
	if (pthread_create(&(simulation.semThreads[simulation.num_semaphores]), &attr, 
		(void * (*)(void*)) startSemaphore, (void *) (simulation.semaphores[simulation.num_semaphores])))
		fatal("trafficSimulator: Error while initializing drawer thread");
	
	semID++;
	changeDuration(simulation.semaphores[simulation.num_semaphores],
		simulation.semSpeedIncrement-semSpeed);
	simulation.num_semaphores++;

	actalizeInfoInScreen(SEM_INFO);
	
	
}

static void
actalizeInfoInScreen(int infoType)
{
	char info[MAX_INFO_WIDTH+1];
	char infoNumber[10];


	switch(infoType)
	{
		case SEM_INFO:
			strncpy(info, SEM_INFO_DISPLAY, MAX_INFO_WIDTH);
			sprintf(infoNumber,"%d", simulation.num_semaphores);
			break;
		case CAR_INFO:
			strncpy(info, CAR_INFO_DISPLAY, MAX_INFO_WIDTH);
			sprintf(infoNumber,"%d", simulation.num_cars);
			break;
		case BLOCKS_INFO:
			strncpy(info, NUM_OF_BLOCKS_DISPLAY, MAX_INFO_WIDTH);
			sprintf(infoNumber,"%d",simulation.confValues[NUM_BLOCKS_Y] *
				simulation.confValues[NUM_BLOCKS_X]);
			break;
		case SPEED_INFO:
			strncpy(info, CAR_SPEED_DISPLAY, MAX_INFO_WIDTH);
			sprintf(infoNumber,"%.2f", simulation.speedMultiplier);
			break;
		case SEM_SP_INFO:
			strncpy(info, SEM_SPEED_DISPLAY, MAX_INFO_WIDTH);
			sprintf(infoNumber,"%d", simulation.semSpeedIncrement);
			strcat(info, infoNumber);
			strcat(info, " sec.");
			printInfo(simulation.drawer, infoType, info);
			drawInfo(simulation.drawer);
			return;
			

	}

	strcat(info, infoNumber);
	printInfo(simulation.drawer, infoType, info);
	drawInfo(simulation.drawer);
}

/* delete a car */
static void
destroyCar(void)
{

	if (simulation.num_cars <= 0)
		return;
	
	freeCar(simulation.cars[simulation.num_cars - 1]);
	simulation.num_cars--;

	actalizeInfoInScreen(CAR_INFO);
}	

/* delete a semaphore */
static void
destroySemaphore(void)
{
	int x, y;	

	if (simulation.num_semaphores <= 0)
		return;
	
	x = getSemX(simulation.semaphores[simulation.num_semaphores - 1]);
	y = getSemY(simulation.semaphores[simulation.num_semaphores - 1]);	

	removeObject(simulation.drawer, x, y);
	removeObject(simulation.drawer, x + 1, y);
	removeObject(simulation.drawer, x, y + 1);
	removeObject(simulation.drawer, x + 1, y + 1);
	draw(simulation.drawer);
	pthread_cancel(simulation.semThreads[simulation.num_semaphores - 1]);
	
	unRegisterSemaphore(simulation.city, x, y);
	freeSemaphore(simulation.semaphores[simulation.num_semaphores - 1]);
	simulation.num_semaphores--;

	actalizeInfoInScreen(SEM_INFO);
}	

/* multiplies speed of all cars */
static void
changeCarsSpeed(double mlt)
{
	int i;

	if(simulation.speedMultiplier * mlt < MAX_CAR_SIM_SPEED)
	{
		for (i = 0; i < simulation.num_cars; i++)
			multiplySpeed(simulation.cars[i], mlt);
	
	
		simulation.speedMultiplier *= mlt;
		actalizeInfoInScreen(SPEED_INFO);
	}
}

/* reset speed of all cars */
static void
resetCars(void)
{
	int i;
	
	for (i = 0; i < simulation.num_cars; i++)
		resetSpeeds(simulation.cars[i]);

	simulation.speedMultiplier = 1.0;
	actalizeInfoInScreen(SPEED_INFO);
}

/* change duration of all semaphores */
static void
changeSemDuration(int step)
{
	int i;

	for (i = 0; i < simulation.num_semaphores; i++)
		changeDuration(simulation.semaphores[i], step);

	simulation.semSpeedIncrement += step;
	actalizeInfoInScreen(SEM_SP_INFO);	
}


